package ee.ut.cs.mc.madp.p2pdivider;

import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.IntentFilter;
import android.net.wifi.p2p.WifiP2pDevice;
import android.net.wifi.p2p.WifiP2pInfo;
import android.net.wifi.p2p.WifiP2pManager;
import android.net.wifi.p2p.WifiP2pManager.Channel;
import android.net.wifi.p2p.WifiP2pManager.ConnectionInfoListener;
import android.net.wifi.p2p.WifiP2pManager.PeerListListener;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Handler.Callback;
import android.os.Message;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.widget.Toast;
import ee.ut.cs.mc.madp.p2pdivider.networking.DividerServer;
import ee.ut.cs.mc.madp.p2pdivider.task.DividerClientTask;
import ee.ut.cs.mc.madp.p2pdivider.task.DividerHostTask;
import ee.ut.cs.mc.madp.p2pdivider.task.PropagateIpTask;
import ee.ut.cs.mc.madp.p2pdivider.task.ReceiveGreetingsTask;
import ee.ut.cs.mc.madp.p2pdivider.task.SendGreetingsTask;

/**
 * @author Kaarel Jõgeva
 *
 */
public abstract class BaseDividerActivity extends FragmentActivity implements ConnectionInfoListener, Callback {

    public static final String TAG = "BaseDividerActivity";
    public static final char OP_SEPARATOR = '¤';

    protected boolean wifiEnabled = false;

    protected WifiP2pManager manager;
    protected Channel channel;
    protected BroadcastReceiver receiver = null;

    protected WifiP2pInfo info;
    protected InetAddress host;
    private boolean greeted = false;

    public static String SERVERIP;
    public static final int SERVERPORT = 8080;
    public static final int SERVERPORT2 = 8081;

    private final Handler handler = new Handler(this);
    private final Handler hostHandler = null;

    public static final int HANDLER_TOAST = 0;
    public static final int HANDLER_START_CLIENT = 1;
    public static final int HANDLER_START_HOST = 2;
    public static final int HANDLER_FILE = 3;
    private static final int HANDLER_HOST_HANDLER = 4;
    public static final int HANDLER_STOP_GREETER = 5;

    private DividerServer server = null;

    private final IntentFilter intentFilter = new IntentFilter();

    private final List<AsyncTask<?, ?, ?>> runningTasks = new ArrayList<AsyncTask<?, ?, ?>>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // add necessary intent values to be matched.
        intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
        intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
        intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
        intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);

        // Setup the WiFi
        manager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
        channel = manager.initialize(this, getMainLooper(), null);

        receiver = new WiFiDirectBroadcastReceiver(manager, channel, this);
        registerReceiver(receiver, intentFilter);
    }

    /** register the BroadcastReceiver with the intent values to be matched */
    @Override
    public void onResume() {
        super.onResume();
        receiver = new WiFiDirectBroadcastReceiver(manager, channel, this);
        registerReceiver(receiver, intentFilter);
    }

    public boolean propagateIPs() {
        say("Propagate start!");
        if (info == null) {
            say("Connection info is null! Please try again...");
            return false;
        }
        if (receiveGreetingsTask != null) {
            receiveGreetingsTask.cancel(true);
        }
        registerTask(new PropagateIpTask(handler, getServer(), info)).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, host); // Wait for the host ip propagation
        say("Propagate end!");

        return true;
    }

    @Override
    public void onPause() {
        super.onPause();
        unregisterReceiver(receiver);
        if (server != null) {
            try {
                server.stop();
            } catch (IOException e) {
            } catch (InterruptedException e) {
            }
        }

        stopTasks();

    }

    public void reset() {
        // TODO - reset state
    }

    public boolean isWifiEnabled() {
        return wifiEnabled;
    }

    public void setWifiEnabled(boolean wifiEnabled) {
        this.wifiEnabled = wifiEnabled;
    }

    public abstract void showFile(String name);

    public abstract PeerListListener getPeerListListener();

    public void deviceUpdated(WifiP2pDevice parcelableExtra) {
        // TODO Auto-generated method stub
    }

    @Override
    protected void onStop() {
        super.onStop();
        try {
            unregisterReceiver(receiver);
        } catch (RuntimeException rte) {
            // The receiver wasn't registered. Nom-nom.
        }
    }

    public WifiP2pManager getManager() {
        return manager;
    }

    public Channel getChannel() {
        return channel;
    }

    @Override
    public void onConnectionInfoAvailable(WifiP2pInfo info) {
        this.info = info;
        host = info.groupOwnerAddress;

        Log.i(TAG, "ConnectionInfo available: " + info.groupOwnerAddress);

        if (!info.groupFormed) {
            Log.i(TAG, "ConnectionInfo available, group hasn't been formed.");
            return;
        }

        if (info.isGroupOwner && receiveGreetingsTask != null) { // Wait for requests to gather all IPs
            AsyncTask<Void, Void, Void> registerTask = registerTask(new ReceiveGreetingsTask(getServer()));
            receiveGreetingsTask = registerTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); // FIXME When played, stop this server task
        } else if (!greeted) { // Be polite and greet the group owner
            boolean isAppHost = this instanceof HostActivity;
            registerTask(new SendGreetingsTask(handler, info, isAppHost)).execute(info.groupOwnerAddress);
            greeted = true;
        }
    }

    private <Params, Progress, Result> AsyncTask<Params, Progress, Result> registerTask(AsyncTask<Params, Progress, Result> asyncTask) {
        runningTasks.add(asyncTask);
        return asyncTask;
    }

    @SuppressWarnings("unchecked")
    private <Params, Progress, Result> void stopTasks() {
        for (Iterator<AsyncTask<?, ?, ?>> iterator = runningTasks.iterator(); iterator.hasNext();) {
            AsyncTask<Params, Progress, Result> type = (AsyncTask<Params, Progress, Result>) iterator.next();
            if (type.isCancelled() || type.cancel(true)) {
                iterator.remove();
            }
        }

        // Sanity check
        if (!runningTasks.isEmpty()) {
            throw new RuntimeException("Failed to close background tasks!");
        }
    }

    @Override
    public boolean handleMessage(Message msg) {
        switch (msg.what) {
        case HANDLER_TOAST:
            say(msg.obj.toString());
            return true;
        case HANDLER_START_CLIENT:
            host = (InetAddress) msg.obj;
            // Start the client
            // XXX FIXME: THIS IS PROBABLY A LEAK!!!
            registerTask(new DividerClientTask(handler, this)).execute(host);
            return true;
        case HANDLER_START_HOST:
            registerTask(new DividerHostTask(handler)).execute();
            return true;
        case HANDLER_HOST_HANDLER:
            break;
        case HANDLER_STOP_GREETER:
            if (receiveGreetingsTask != null) {
                receiveGreetingsTask.cancel(true);
            }
            return true;
        default:
            return false;
        }
        return false;
    }

    private AsyncTask<Void, Void, Void> receiveGreetingsTask = null;

    public void say(String text) {
        Log.i(TAG, text);
        Toast.makeText(this, text, Toast.LENGTH_LONG).show();
    }

    public DividerServer getServer() {
        if (server == null) {
            server = new DividerServer(handler, SERVERPORT);
        }
        return server;
    }
}